#include "info.h"
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>

#ifndef ARRAY_SIZE
  #define ARRAY_SIZE(A) (sizeof(A)/sizeof(A[0]))
#endif

// CPUID指令
#define cpuid(func,eax,ebx,ecx,edx)             \
  __asm__ __volatile__ ("cpuid":                \
  "=a" (eax),"=b" (ebx),"=c" (ecx),"=d" (edx):  \
  "a" (func));

// 获取CPU ID
// 功能号eax=0x80000004，返回CPU ID。执行cpuid指令后，返回值寄存器依次为eax,ebx,ecx,edx
void cpuinfo::get_id(int &a, int &b, int &c, int &d) {
  cpuid(0x80000004, a, b, c, d);
}

// 获取CPU 厂商
// 功能号eax=0，返回CPU 厂商。执行cpuid指令后，返回值寄存器依次为ebx,ecx,edx
void cpuinfo::get_company(std::string &data) {
  char com_name[13] = { 0 };
  int a, b, c, d;
  int i, k;

  cpuid(0, a, b, c, d);

  for (i = 0; b > 0; i++) {
    k = b;
    k = (k>>8);
    k = (k<<8);
    com_name[i] = b - k;
    b = (b>>8);
  }
  for (; d > 0; i++) {
    k = d;
    k = (k>>8);
    k = (k<<8);
    com_name[i] = d - k;
    d = (d>>8);
  }
  for (; c > 0; i++) {
    k = c;
    k = (k>>8);
    k = (k<<8);
    com_name[i] = c - k;
    c = (c>>8);
  }
  com_name[12]='\0';
  data = com_name;
}

// 获取CPU Brand
// EAX=80000002h、80000003h、80000004h：返回处理器brand字符串
bool cpuinfo::get_brand(std::string &data) {
  int a,b,c,d;
  unsigned int cpu_brand_buf[13] = { 0 };
  int k = 0;

  /**
  * eax == 0x800000000
  * 如果CPU支持Brand String，则在EAX中返 >= 0x80000004的值。
  */
  cpuid(0x80000000, a, b, c, d);
  if (a < 0x80000004) {
    // the cpu is not support
    return false;
  }

  for (int i = 0x80000002; i <= 0x80000004; i++) {
    cpuid(i, a, b, c, d);
    cpu_brand_buf[k++] = a;
    cpu_brand_buf[k++] = b;
    cpu_brand_buf[k++] = c;
    cpu_brand_buf[k++] = d;
  }
  data = (char*)cpu_brand_buf;
  return true;
}

// 获取CPU Family,Model,Stepping ID
// AX=0x01，返回Model、Family、Stepping信息.执行CPUID后，返回的EAX寄存器包含Model、Family、Stepping信息
void cpuinfo::get_base_param(int &family, int &model, int &stepping) {
  unsigned long info;
  int a, b, c, d;

  cpuid(1, a, b, c, d);
  info = a;

  family = (info & 0x0F00) >> 8;
  model = (info & 0xF0) >> 4;
  stepping = info & 0xF;
}

// 返回系统所有的CPU数量，包括系统中禁止用户使用的CPU个数
long cpuinfo::get_count() {
  return sysconf( _SC_NPROCESSORS_CONF);
}
// 返回系统中可用的CPU个数
long cpuinfo::get_count_enabled() {
  return sysconf(_SC_NPROCESSORS_ONLN);
}

//----------------------------//

// 系统中物理页数个数
long sysinfo::pages() {
  return sysconf(_SC_PHYS_PAGES);
}
// 系统中可用的页面个数
long sysinfo::pages_enabled() {
  return sysconf(_SC_AVPHYS_PAGES);
}
// 系统页面的大小
long sysinfo::page_size() {
  return sysconf(_SC_PAGESIZE);
}

// 物理内存
long long sysinfo::size() {
  long long pages = sysinfo::pages();
  long long size = sysinfo::page_size();
  return pages * size;
}
// 空闲的物理内存
long long sysinfo::size_free() {
  long long pages = sysinfo::pages_enabled();
  long long size = sysinfo::page_size();
  return pages * size;
}

bool sysinfo::uname(struct utsname *buf) {
  return ::uname(buf) == 0;
}

int64_t sysinfo::install_time() {
  static const char* sdirs[] = {
    "/boot/lost+found/",
    "/lost+found/",
    "/boot/",
  };

  struct stat buf;
  for (int i = 0; i < ARRAY_SIZE(sdirs); ++i) {
    if (0 == stat(sdirs[i], &buf)) {
      return buf.st_ctime;
    }
  }
  return 0;
}

//----------------------------//

void printinfo::os_info() {
  FILE *fp = fopen("/proc/version", "r");
  if (NULL == fp)
      printf("failed to open version\n");
  char szTest[1000] = { 0 };
  while (!feof(fp)) {
      memset(szTest, 0, sizeof(szTest));
      fgets(szTest, sizeof(szTest) - 1, fp); // leave out \n
      printf("%s", szTest);
  }
  fclose(fp);
}
void printinfo::cpu_info() {
  FILE *fp = fopen("/proc/cpuinfo", "r");
  if (NULL == fp)
      printf("failed to open cpuinfo\n");
  char szTest[1000] = { 0 };
  // read file line by line
  while (!feof(fp)) {
      memset(szTest, 0, sizeof(szTest));
      fgets(szTest, sizeof(szTest) - 1, fp); // leave out \n
      printf("%s", szTest);
  }
  fclose(fp);
}
void printinfo::memory_info() {
  FILE *fp = fopen("/proc/meminfo", "r");
  if (NULL == fp)
      printf("failed to open meminfo\n");
  char szTest[1000] = {0};
  while (!feof(fp)) {
      memset(szTest, 0, sizeof(szTest));
      fgets(szTest, sizeof(szTest) - 1, fp);
      printf("%s", szTest);
  }
  fclose(fp);
}
void printinfo::disk_info() {

}
void printinfo::network_info() {

}

//----------------------------//

uid_t sysinfo::cur_user() {
  return getuid();
}
gid_t sysinfo::cur_group() {
  struct passwd pwd;
  if (!user(cur_user(), pwd))
    return 0;
  return pwd.pw_gid;
}
bool sysinfo::user(uid_t uid, struct passwd &pwd) {
  struct passwd *tmp = ::getpwuid(uid);
  if (!tmp) return false;
  pwd = *tmp;
  return true;
}
bool sysinfo::user(const char *name, struct passwd &pwd) {
  struct passwd *tmp = getpwnam(name);
  if (!tmp) return false;
  pwd = *tmp;
  return true;
}
bool sysinfo::group(gid_t gid, sgup_info &gup) {
  struct group *data;
  while((data = getgrent()) != 0) {
    if (gid != data->gr_gid)
      continue;
    gup.gid = data->gr_gid;
    gup.name = data->gr_name;
    gup.users.clear();
    int i = 0;
    while(data->gr_mem[i]) {
      gup.users.insert(data->gr_mem[i]);
      ++i;
    }
    endgrent();
    return true;
  }
  endgrent();
  return false;
}

void sysinfo::groupusers(sgu_info &gui) {
  struct group *data;
  while((data = getgrent()) != 0) {
    sgup_info gup;
    gup.gid = data->gr_gid;
    gup.name = data->gr_name;
    int i = 0;
    while(data->gr_mem[i]) {
      gup.users.insert(data->gr_mem[i]);
      gui.users.insert(data->gr_mem[i]);
      ++i;
    }
    gui.groups.push_back(gup);
  }
  endgrent();
}

void sysinfo::loginout_info(std::vector<sloginout_info> &info) {
  struct utmp *p_utent;

  utmpname(_PATH_WTMP); /* #define _PATH_WTMP "/var/log/wtmp" */

  setutent(); /* rewinds the file pointer to the beginning of the utmp file */

  info.clear();

  //printf("type\tline\tpid\tid\tuser\thost\tsession\texit\t\ttime\n");

  while((p_utent = getutent()) != NULL){
    if(p_utent->ut_type == USER_PROCESS) {
      sloginout_info si;
      si.line = p_utent->ut_line;
      si.host = p_utent->ut_host;
      si.user = p_utent->ut_user;
      si.login.sec = p_utent->ut_tv.tv_sec;
      si.login.usec = p_utent->ut_tv.tv_usec;
      si.logout.sec = 0LL;
      si.logout.usec = 0LL;
      si.still = true;
      info.push_back(si);
    } else if(p_utent->ut_type == DEAD_PROCESS) {
      sloginout_info *si = NULL;
      for (std::vector<sloginout_info>::iterator it = info.begin(); it != info.end(); ++it) {
        if (!it->still) continue;
        if (it->line != p_utent->ut_line) continue;
        if (it->login.sec > p_utent->ut_tv.tv_sec)
          continue;
        si = &(*it);
        break;
      }
      if (!si) continue;

      si->logout.sec = p_utent->ut_tv.tv_sec;
      si->logout.usec = p_utent->ut_tv.tv_usec;
      si->still = false;
    }
    
    /*
    switch (p_utent->ut_type) {
      case EMPTY: {
        printf("EMPTY");
      } break;
      case RUN_LVL: {
        printf("RUN_LVL");

      } break;
      case BOOT_TIME: {
        printf("BOOT_TIME");

      } break;
      case OLD_TIME: {
        printf("OLD_TIME");

      } break;
      case NEW_TIME: {
        printf("NEW_TIME");

      } break;
      case INIT_PROCESS: {
        printf("INIT_PROCESS");

      } break;
      case LOGIN_PROCESS: {
        printf("LOGIN_PROCESS");

      } break;
      case USER_PROCESS: {
        printf("USER_PROCESS");

      } break;
      case DEAD_PROCESS: {
        printf("DEAD_PROCESS");

      } break;
      case ACCOUNTING: {
        printf("ACCOUNTING");

      } break;
    }

    long t = p_utent->ut_tv.tv_sec;
    printf("\t%s\tpid:%d\tid:%d\tuser:%s\thost:%s\tsession:%d\t(%d,%d)\t%s"
      , p_utent->ut_line
      , p_utent->ut_pid
      , p_utent->ut_id
      , p_utent->ut_user
      , p_utent->ut_host
      , p_utent->ut_session
      , p_utent->ut_exit.e_termination
      , p_utent->ut_exit.e_exit
      , ctime(&t));
    */
  }
  endutent(); /* closes the utmp file. */
}


void sysinfo::poweronoff_info(std::vector<spoweronoff_info> &info) {
  struct utmp *p_utent;

  utmpname(_PATH_WTMP); /* #define _PATH_WTMP "/var/log/wtmp" */

  setutent(); /* rewinds the file pointer to the beginning of the utmp file */

  info.clear();

  while((p_utent = getutent()) != NULL){
    if(p_utent->ut_type == BOOT_TIME
      && strcmp(p_utent->ut_user, "reboot") == 0) {
      spoweronoff_info si;
      si.on.sec = p_utent->ut_tv.tv_sec;
      si.on.usec = p_utent->ut_tv.tv_usec;
      si.off.sec = 0LL;
      si.off.usec = 0LL;
      si.still = true;
      info.push_back(si);
    } else if(p_utent->ut_type == RUN_LVL
      && strcmp(p_utent->ut_user, "shutdown") == 0) {
      spoweronoff_info *si = NULL;
      for (std::vector<spoweronoff_info>::iterator it = info.begin(); it != info.end(); ++it) {
        if (!it->still) continue;
        if (it->on.sec > p_utent->ut_tv.tv_sec)
          continue;
        si = &(*it);
        break;
      }
      if (!si) continue;

      si->off.sec = p_utent->ut_tv.tv_sec;
      si->off.usec = p_utent->ut_tv.tv_usec;
      si->still = false;
    }
  }
  endutent(); /* closes the utmp file. */
}

std::string sysinfo::format_date(int64_t tm) {
  char buffer[128] = { 0 };
  struct tm ltm;
  localtime_r(&tm, &ltm);
  sprintf(buffer,"%4.4d/%2.2d/%2.2d %2.2d:%2.2d:%2.2d"
    , ltm.tm_year + 1900
    , ltm.tm_mon + 1
    , ltm.tm_mday
    , ltm.tm_hour
    , ltm.tm_min
    , ltm.tm_sec);
  return buffer;
}

std::string sysinfo::format_time(int64_t tm) {
  int d = 0;
  int h = 0;
  int m = 0;
  int s = 0;

  s = tm % 60;
  tm /= 60;

  if (tm > 0) {
    m = tm % 60;
    tm /= 60;

    if (tm > 0) {
      h = tm % 60;
      d = tm / 24;
    }
  }

  char buffer[128] = { 0 };
  if (d > 0) {
    sprintf(buffer,"%d %2.2d:%2.2d:%2.2d"
      , d, h, m, s);
  } else {
    sprintf(buffer,"%2.2d:%2.2d:%2.2d"
      , h, m, s);
  }

  return buffer;
}

#undef cpuid